package com.example.demo.swj.uitl;

import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.HexUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.symmetric.SymmetricCrypto;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.signers.PlainDSAEncoding;
import org.bouncycastle.crypto.signers.StandardDSAEncoding;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;

/**
 * @author baidh
 * @date 2020/09/21
 */
public class SMUtil {

    public static final SM2Engine.Mode SM2_MODE = SM2Engine.Mode.C1C2C3;
    public static final String SM2_PUB_PREFIX = "04";

    public static class Sm2Key {
        private final String priKey;
        private final String pubKey;

        public Sm2Key(String priKey, String pubKey) {
            this.priKey = priKey;
            this.pubKey = pubKey;
        }

        public String getPriKey() {
            return priKey;
        }

        public String getPubKey() {
            return pubKey;
        }
    }

    /**
     * SM2 生成秘钥
     *
     * @return Sm2Key
     */
    public static Sm2Key genSM2Key() {
        NSM2 sm2 = SmUtils.sm2();
        sm2.setMode(SM2_MODE);
        final byte[] priBytes = ((BCECPrivateKey) sm2.getPrivateKey()).getD().toByteArray();
        final byte[] pubBytes = ((BCECPublicKey) sm2.getPublicKey()).getQ().getEncoded(false);
        return new Sm2Key(HexUtil.encodeHexStr(priBytes), HexUtil.encodeHexStr(pubBytes));
    }

    /**
     * SM2 加密算法
     *
     * @param pubKey  16进制公钥
     * @param content 加密前的内容
     * @return 加密后16进制字符串
     */
    public static String sm2Encrypt(String pubKey, String content) {
        if (pubKey.startsWith(SM2_PUB_PREFIX)) {
            pubKey = pubKey.substring(2);
        }

        NSM2 sm2 = new NSM2(null, pubKey.substring(0, 64), pubKey.substring(64));
        sm2.setMode(SM2_MODE);
        return sm2.encryptBcd(content, KeyType.PublicKey);
    }

    /**
     * SM2 解密算法
     *
     * @param priKey     16进制私钥
     * @param encryptHex 加密后16进制字符串
     * @return 加密前的内容
     */
    public static String sm2Decrypt(String priKey, String encryptHex) {
        NSM2 sm2 = new NSM2(priKey, null, null);
        sm2.setMode(SM2_MODE);
        return StrUtil.utf8Str(sm2.decryptFromBcd(encryptHex, KeyType.PrivateKey));
    }
    public static BigInteger toBigInteger(String hexStr) {
        return null == hexStr ? null : new BigInteger(hexStr, 16);
    }


    /**
     * SM2 签名算法
     *
     * @param priKey  16进制私钥
     * @param IDA     用户标识
     * @param content 加签原文
     * @return 签名[R, S]的数组
     * @throws UnsupportedEncodingException
     */
    public static BigInteger[] sm2Sign(String priKey, String IDA, String content) throws UnsupportedEncodingException {
        final PlainDSAEncoding dsaEncoding = new PlainDSAEncoding();
        NSM2 sm2 = new NSM2(priKey, null, null);
        sm2.setEncoding(dsaEncoding);
        final byte[] data = content.getBytes("UTF-8");
        final byte[] sign = sm2.sign(data, IDA.getBytes());

        final BigInteger n = BCUtils.toSm2Params(toBigInteger(priKey)).getParameters().getN();
        return dsaEncoding.decode(n, sign);
    }

    /**
     * SM2 验签算法
     *
     * @param pubKey  16进制公钥
     * @param IDA     用户标识
     * @param content 加签原文
     * @param rs      签名RS的数组
     * @return 验签是否通过
     * @throws IOException
     */
    public static boolean sm2SignValid(String pubKey, String IDA, String content, BigInteger[] rs) throws IOException {
        if (pubKey.startsWith(SM2_PUB_PREFIX)) {
            pubKey = pubKey.substring(2);
        }

        NSM2 sm2 = new NSM2(null, pubKey.substring(0, 64), pubKey.substring(64));
        final byte[] data = content.getBytes("UTF-8");

        final BigInteger n = BCUtils.toSm2Params(pubKey.substring(0, 64), pubKey.substring(64)).getParameters().getN();
        final byte[] sign = StandardDSAEncoding.INSTANCE.encode(n, rs[0], rs[1]);

        return sm2.verify(data, sign, IDA.getBytes());
    }

    /**
     * 转换RS数组字符串
     *
     * @param rs [R,S]签名数组
     * @return
     */
    public static String sm2SignStr(BigInteger[] rs) {
        return rs[0] + "," + rs[1];
    }

    /**
     * 解析RS数组字符串
     *
     * @param rsStr RS数组字符串
     * @return [R, S]签名数组
     */
    public static BigInteger[] sm2SignStr(String rsStr) {
        final String[] rs = rsStr.split(",");
        return new BigInteger[]{new BigInteger(rs[0], 10), new BigInteger(rs[1], 10),};
    }

    /**
     * SM3 哈希算法
     *
     * @param content 原文内容
     * @return 16进制散列值
     */
    public static String sm3(String content) {
        return SmUtils.sm3(content);
    }

    /**
     * SM4 生成秘钥
     *
     * @return 16进制秘钥
     */
    public static String genSM4Key() {
        final byte[] bytes = SmUtils.sm4().getSecretKey().getEncoded();
        return HexUtil.encodeHexStr(bytes);
    }

    /**
     * SM4 加密算法
     *
     * @param key     16进制秘钥
     * @param content 加密前的内容
     * @return 加密后16进制字符串
     */
    public static String sm4Encrypt(String key, String content) {
        NSymmetricCrypto sm4 = SmUtils.sm4(HexUtil.decodeHex(key));
        return sm4.encryptHex(content);
    }

    /**
     * SM4 解密算法
     *
     * @param key        16进制秘钥
     * @param encryptHex 加密后16进制字符串
     * @return 加密前的内容
     */
    public static String sm4Decrypt(String key, String encryptHex) {
        NSymmetricCrypto sm4 = SmUtils.sm4(HexUtil.decodeHex(key));
        return sm4.decryptStr(encryptHex, CharsetUtil.CHARSET_UTF_8);

    }

    public static void main(String[] args) throws Exception {
        demo();
//        test();
    }

    private static void demo() throws Exception {
        final String content = "测试中文*1234";
        final String IDA = "testUser";

        final Sm2Key sm2Key = genSM2Key();
        final String sm2Encrypt = sm2Encrypt(sm2Key.getPubKey(), content);
        final String sm2Decrypt = sm2Decrypt(sm2Key.getPriKey(), sm2Encrypt);

        System.out.println("--SM2--");
        System.out.println(sm2Key);
        System.out.println(sm2Encrypt);
        System.out.println(sm2Decrypt);

        final BigInteger[] signRs = sm2Sign(sm2Key.getPriKey(), IDA, content);
        final String sm2SignStr = sm2SignStr(signRs);
        final BigInteger[] sm2SignRs = sm2SignStr(sm2SignStr);
        final boolean signValid = sm2SignValid(sm2Key.getPubKey(), IDA, content, sm2SignRs);

        System.out.println("--SM2 SIGN--");
        System.out.println(sm2SignStr);
        System.out.println(signValid);

        System.out.println("--SM3--");
        final String sm3 = sm3(content);
        System.out.println(sm3);

        System.out.println("--SM4--");
        final String sm4Key = genSM4Key();
        final String sm4Encrypt = sm4Encrypt(sm4Key, content);
        final String sm4Decrypt = sm4Decrypt(sm4Key, sm4Encrypt);
        System.out.println(sm4Key);
        System.out.println(sm4Encrypt);
        System.out.println(sm4Decrypt);
    }

    private static void test() throws Exception {
        System.out.println("--SM2--");
        final Sm2Key sm2Key = new Sm2Key("2e3527cf06e7fc2028c643102f9806d6fc2b3217709cce706d01c552d2bc010a", "0440df5b8862001f5a867bea9397661ecb616caf947f7c6b3393edd29f3aa61acc42ff7d1a7988892d8a2b938cddd318be3e4d13de3f7772b2da8a771e29d6face");
        final String sm2Decrypt1 = sm2Decrypt(sm2Key.getPriKey(), "04C7F1B0F75E656DD0736A95CF69DE4E41DD075AC2C0690E6D08401A6A40F08B4C8AEF8EA171F022338F4EB51EA9FAC9D1D700457EE3C1F60F4316F4E1C72B04547EE3647AE34959DFD2F042CEF20DBB8A4906282B86C1CC0657914A81643EB03C290D6F13E28B18D9BB16");
        final String sm2Decrypt2 = sm2Decrypt(sm2Key.getPriKey(), "04650475AA003DA8ABE3B6A785A239CBD25CC479CDC85F0B5B08415A82C6239F6E7F7A118DE7F8D0B2B6E8B19C035AB8F0350B74D2A3899CB4EB4453967D3967FE613AE61EFE3A95E052E0D965416B38CEAF340F67B13F451152ACD3C03E0182B98D8A9F713E7DDAB9FFE676A50021C0");
        System.out.println(sm2Key);
        System.out.println(sm2Decrypt1);
        System.out.println(sm2Decrypt2);

        System.out.println("--SM3--");
        final String sm3 = sm3("测试中文123Abc@*");
        System.out.println(sm3);

        System.out.println("--SM4--");
        final String sm4Key = "072028053dcba3c6a74cb00869fd5118";
        final String sm4Encrypt = sm4Encrypt(sm4Key, "test中文");
        final String sm4Decrypt = sm4Decrypt(sm4Key, sm4Encrypt);
        System.out.println(sm4Key);
        System.out.println(sm4Encrypt);
        System.out.println(sm4Decrypt);

        System.out.println("--SM2 SIGN--");
        final Sm2Key sm2SignKey = new Sm2Key("2e3527cf06e7fc2028c643102f9806d6fc2b3217709cce706d01c552d2bc010a", "0440df5b8862001f5a867bea9397661ecb616caf947f7c6b3393edd29f3aa61acc42ff7d1a7988892d8a2b938cddd318be3e4d13de3f7772b2da8a771e29d6face");
        final String IDA = "123456789";
        final String content = "a=中文&b=123&UPPER=TRUE";
        final BigInteger[] signRs = new BigInteger[]{toBigInteger("5BC5B7C6ECEF7197CB4D3270596240BE8EFF4BA37997BC08B997A19E38217D73"), toBigInteger("B69E928BED5A41CAC9528F477D295765638144F6287F0962718CD18A722D465A")};
        final boolean signValid = sm2SignValid(sm2Key.getPubKey(), IDA, content, signRs);
        System.out.println(signRs[0] + "," + signRs[1]);
        System.out.println(signValid);
    }

}
